package com.arpa.wms.common.util;

import cn.hutool.http.HttpUtil;
import com.google.gson.*;
import com.google.gson.internal.LinkedTreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author liuxr
 * @date 2021年09月04日 7:44
 */
public class DouyinUtil {

    String host = "https://openapi.jinritemai.com";
    static String appKey= "6995344980931790370";
    static String appSecret = "6788d817-1a16-4acc-b6bd-633261a2a9c4";

    public static String getPrintParam(String accessToken){
        String method = "logistics.getShopKey";

        long timestamp = System.currentTimeMillis() / 1000;
        Map<String, Object> m = new HashMap<>();
        // 序列化参数
        String paramJson = marshal(m);
        System.out.println("param_json:" + paramJson);
        // 计算签名
        String signVal = sign(appKey, appSecret, method, timestamp, paramJson);
        return ("access_token="+accessToken+"&app_key="+appKey+"&method=logistics.getShopKey&param_json={}&timestamp="+timestamp+"&v=2&sign=" + signVal);

    }
    // 调用Open Api，取回数据
    public static String fetch(String appKey, String host, String method, long timestamp, String paramJson, String accessToken, String sign) throws IOException {
        String methodPath = method.replace('.', '/');

        String u = host + "/" + methodPath +
                "?method=" + HttpUtil.encodeParams(method, StandardCharsets.UTF_8) +
                "&app_key=" + HttpUtil.encodeParams(appKey, StandardCharsets.UTF_8) +
                "&access_token=" + HttpUtil.encodeParams(accessToken, StandardCharsets.UTF_8) +
                "&timestamp=" + HttpUtil.encodeParams(Long.toString(timestamp), StandardCharsets.UTF_8) +
                "&v=" + HttpUtil.encodeParams("2", StandardCharsets.UTF_8) +
                "&sign=" + HttpUtil.encodeParams(sign, StandardCharsets.UTF_8);

        Map<String,Object> params = new HashMap<>();
        params.put("param_json",paramJson);
        return  HttpUtil.post(u,params);
    }
    // 计算签名
    public static String sign(String appKey, String appSecret, String method, long timestamp, String paramJson) {
        // 按给定规则拼接参数
        String paramPattern = "app_key" + appKey + "method" + method + "param_json" + paramJson + "timestamp" + timestamp + "v2";
        String signPattern = appSecret + paramPattern + appSecret;
        System.out.println("sign_pattern:" + signPattern);

        try {
            return stringToMD5(signPattern);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return hmac(signPattern, appSecret);
    }

    public static String stringToMD5(String plainText) throws UnsupportedEncodingException {
        byte[] secretBytes = null;
        try {
            secretBytes = MessageDigest.getInstance("md5").digest(
                    plainText.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("没有这个md5算法！");
        }
        StringBuilder md5code = new StringBuilder(new BigInteger(1, secretBytes).toString(16));
        while (md5code.length() < 32) {
            md5code.insert(0, "0");
        }
        return md5code.toString();
    }

    // 计算hmac
    public static String hmac(String plainText, String appSecret) {
        Mac mac;
        try {
            byte[] secret = appSecret.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");

            mac = Mac.getInstance("HmacSHA256");
            mac.init(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            return "";
        }

        byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
        byte[] digest = mac.doFinal(plainBytes);
        StringBuilder sb = new StringBuilder();
        for (byte b: digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 序列化参数
// 这一步看上去冗余，实际很重要。如果要自己实现，则必须保证这三点：
// 1、保证JSON所有层级上Key的有序性
// 2、保证JSON的所有数值不带多余的小数点
// 3、保证转义逻辑与这段代码一致
    public static String marshal(Object o) {
        String raw = CustomGson.toJson(o);
        Map<?, ?> m = CustomGson.fromJson(raw, LinkedTreeMap.class); // 执行反序列化，把所有JSON对象转换成LinkedTreeMap
        return CustomGson.toJson(m); // 重新序列化，保证JSON所有层级上Key的有序性
    }

    private static final Gson CustomGson = new GsonBuilder()
            .registerTypeAdapter(LinkedTreeMap.class, newMapSerializer()) // 定制LinkedTreeMap序列化，确保所有key按字典序排序
            .registerTypeAdapter(Integer.class, newNumberSerializer()) // 定制数值类型的序列化，确保整数输出不带小数点
            .registerTypeAdapter(Long.class, newNumberSerializer()) // 同上
            .registerTypeAdapter(Double.class, newNumberSerializer()) // 同上
            .disableHtmlEscaping() // 禁用Html Escape，确保符号不转义：&<>='
            .create();

    // 为LinkedTreeMap定制的序列化器
    public static JsonSerializer<LinkedTreeMap<?, ?>> newMapSerializer() {
        return new JsonSerializer<LinkedTreeMap<?, ?>>() {
            @Override
            public JsonElement serialize(LinkedTreeMap<?, ?> src, Type typeOfSrc, JsonSerializationContext context) {
                List<String> keys = src.keySet().stream().map(Object::toString).sorted().collect(Collectors.toList());
                JsonObject o = new JsonObject();
                for (String k : keys) {
                    o.add(k, context.serialize(src.get(k)));
                }
                return o;
            }
        };
    }

    // 为Number定制化的序列化器
    private static <T extends Number> JsonSerializer<T> newNumberSerializer() {
        return new JsonSerializer<T>() {
            @Override
            public JsonElement serialize(T number, Type type, JsonSerializationContext context) {
                if (number instanceof Integer) {
                    return new JsonPrimitive(number.intValue());
                }
                if (number instanceof Long) {
                    return new JsonPrimitive(number.longValue());
                }
                if (number instanceof Double) {
                    long longValue = number.longValue();
                    double doubleValue = number.doubleValue();
                    if (longValue == doubleValue) {
                        return new JsonPrimitive(longValue);
                    }
                }
                return new JsonPrimitive(number);
            }
        };
    }

}
